package com.varian.auth.config;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import com.varian.auth.authentication.VarianAuthentication;
import com.varian.auth.authentication.VarianOAuth2AuthorizationService;
import com.varian.auth.authentication.VarianRegisteredClientRepository;
import com.varian.auth.authentication.handler.VarianAuthenticationSuccessHandler;
import com.varian.auth.authentication.token.VarianOAuth2TokenCustomizers;
import com.varian.auth.service.IOAuth2AuthorizationInfoService;
import com.varian.auth.service.IOAuth2RegisteredClientService;
import com.varian.misc.MiscConstant;
import com.varian.security.constant.SecurityConstant;
import com.varian.security.handler.VarianAccessDeniedHandler;
import com.varian.security.handler.VarianAuthenticationEntryPoint;
import lombok.SneakyThrows;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.token.*;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;

import java.io.IOException;

/**
 * @author ben
 * @since 2024/6/24
 */
@Configuration(proxyBeanMethods = false)
public class AuthConfig {

    @Bean
    @SneakyThrows
    @Order(SecurityConstant.AUTHORIZATION_SECURITY_FILTER_ORDER)
    public SecurityFilterChain authorizationServerSecurityFilterChain(
            HttpSecurity http,
            OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator,
            UserDetailsService userDetailsService,
            OAuth2AuthorizationService authorizationService,
            PasswordEncoder passwordEncoder
    ) {
        OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
                authorizationServerConfigurer(tokenGenerator, userDetailsService, authorizationService, passwordEncoder);
        authorizationServerConfigurer.tokenEndpoint(tokenEndpoint -> tokenEndpoint.accessTokenResponseHandler(new VarianAuthenticationSuccessHandler()));
        RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
        http.securityMatcher(endpointsMatcher)
                .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())
                .csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
                .cors(AbstractHttpConfigurer::disable)
                .with(authorizationServerConfigurer, Customizer.withDefaults());
        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).oidc(Customizer.withDefaults());
        http.exceptionHandling(e ->
                e.defaultAccessDeniedHandlerFor(new VarianAccessDeniedHandler(), AntPathRequestMatcher.antMatcher("/**"))
                        .defaultAuthenticationEntryPointFor(new VarianAuthenticationEntryPoint(), AntPathRequestMatcher.antMatcher("/**")));
        return http.build();
    }

    @Bean
    public OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator(JwtEncoder jwtEncoder) {
        VarianOAuth2TokenCustomizers tokenCustomizers = new VarianOAuth2TokenCustomizers();
        JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder);
        jwtGenerator.setJwtCustomizer(tokenCustomizers.jwtCustomizer());
        OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
        accessTokenGenerator.setAccessTokenCustomizer(tokenCustomizers.accessTokenCustomizer());
        OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
        return new DelegatingOAuth2TokenGenerator(jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
    }

    private OAuth2AuthorizationServerConfigurer authorizationServerConfigurer(
            OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator,
            UserDetailsService userDetailsService,
            OAuth2AuthorizationService authorizationService,
            PasswordEncoder passwordEncoder
    ) {
        OAuth2AuthorizationServerConfigurer config = new OAuth2AuthorizationServerConfigurer();
        VarianAuthentication varianAuthentication = new VarianAuthentication();
        config.authorizationEndpoint(varianAuthentication::authorizationEndpoint)
                .tokenEndpoint(c -> varianAuthentication.tokenEndpoint(c, userDetailsService, tokenGenerator, authorizationService, passwordEncoder));
        return config;
    }

    @Bean
    public JwtEncoder jwtEncoder(JWKSource<SecurityContext> jwkSource) {
        return new NimbusJwtEncoder(jwkSource);
    }

    @Bean
    public OAuth2AuthorizationService authorizationService(IOAuth2AuthorizationInfoService auth2AuthorizationService) {
        return new VarianOAuth2AuthorizationService(auth2AuthorizationService);
    }

    @Bean
    public RegisteredClientRepository registeredClientRepository(IOAuth2RegisteredClientService registeredClientService) {
        return new VarianRegisteredClientRepository(registeredClientService);
    }

    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder()
                .issuer(MiscConstant.VARIAN_ISSUER)
                .build();
    }

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer authJackson2ObjectMapperBuilderCustomizer() {
        return builder -> builder.serializerByType(SimpleGrantedAuthority.class, new StdSerializer<>(SimpleGrantedAuthority.class) {
            @Override
            public void serialize(SimpleGrantedAuthority value, JsonGenerator gen, SerializerProvider provider) throws IOException {
                gen.writeString(value.getAuthority());
            }
        });
    }


}
